import type { DefaultKeyring } from '@keystonehq/sol-keyring';
import type { WalletName } from '@solana/wallet-adapter-base';
import {
    BaseMessageSignerWalletAdapter,
    isVersionedTransaction,
    WalletAccountError,
    WalletLoadError,
    WalletNotConnectedError,
    WalletNotReadyError,
    WalletPublicKeyError,
    WalletReadyState,
    WalletSignTransactionError,
} from '@solana/wallet-adapter-base';
import type { Transaction, TransactionVersion, VersionedTransaction } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';

export interface KeystoneWalletAdapterConfig {}

export const KeystoneWalletName = 'Keystone' as WalletName<'Keystone'>;

export class KeystoneWalletAdapter extends BaseMessageSignerWalletAdapter {
    name = KeystoneWalletName;
    url = 'https://keyst.one';
    icon =
        '';
    supportedTransactionVersions: ReadonlySet<TransactionVersion> = new Set(['legacy', 0]);

    private _keyring: DefaultKeyring | null;
    private _publicKey: PublicKey | null;
    private _connecting: boolean;
    private _readyState: WalletReadyState =
        typeof window === 'undefined' || typeof document === 'undefined'
            ? WalletReadyState.Unsupported
            : WalletReadyState.Loadable;

    get publicKey() {
        return this._publicKey;
    }

    get connecting() {
        return this._connecting;
    }

    get readyState() {
        return this._readyState;
    }

    constructor(config: KeystoneWalletAdapterConfig = {}) {
        super();
        this._keyring = null;
        this._publicKey = null;
        this._connecting = false;
    }

    async connect(): Promise<void> {
        try {
            if (this.connected || this.connecting) return;
            if (this._readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();

            this._connecting = true;

            let keyring: DefaultKeyring;
            try {
                const { DefaultKeyring } = await import('@keystonehq/sol-keyring');
                keyring = DefaultKeyring.getEmptyKeyring();
            } catch (error: any) {
                throw new WalletLoadError(error?.message, error);
            }

            let account: string;
            try {
                await keyring.readKeyring();
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                account = keyring.getAccounts()[0]!.pubKey;
            } catch (error: any) {
                throw new WalletAccountError(error?.message, error);
            }

            let publicKey: PublicKey;
            try {
                publicKey = new PublicKey(account);
            } catch (error: any) {
                throw new WalletPublicKeyError(error?.message, error);
            }

            this._keyring = keyring;
            this._publicKey = publicKey;
            this.emit('connect', publicKey);
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        } finally {
            this._connecting = false;
        }
    }

    async disconnect(): Promise<void> {
        if (this._keyring) {
            this._keyring = null;
            this._publicKey = null;
        }

        this.emit('disconnect');
    }

    async signTransaction<T extends Transaction | VersionedTransaction>(transaction: T): Promise<T> {
        try {
            const keyring = this._keyring;
            const publicKey = this._publicKey;
            if (!keyring || !publicKey) throw new WalletNotConnectedError();

            try {
                if (isVersionedTransaction(transaction)) {
                    const txHex = transaction.serialize();
                    const signature = await keyring.signTransaction(publicKey.toString(), txHex);
                    transaction.addSignature(publicKey, signature);
                } else {
                    const txHex = transaction.serializeMessage();
                    const signature = await keyring.signTransaction(publicKey.toString(), txHex);
                    transaction.addSignature(publicKey, Buffer.from(signature));
                }
                return transaction;
            } catch (error: any) {
                throw new WalletSignTransactionError(error?.message, error);
            }
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        }
    }

    async signMessage(message: Uint8Array): Promise<Uint8Array> {
        try {
            const keyring = this._keyring;
            const publicKey = this._publicKey?.toString();
            if (!keyring || !publicKey) throw new WalletNotConnectedError();

            try {
                return keyring.signMessage(publicKey, message);
            } catch (error: any) {
                throw new WalletSignTransactionError(error?.message, error);
            }
        } catch (error: any) {
            this.emit('error', error);
            throw error;
        }
    }
}
